
package com.studiofortress.sf.util.structure;

import com.studiofortress.sf.structure.MainLoopEvent;
import com.studiofortress.sf.graphics.GraphicsGL;
import com.studiofortress.sf.structure.MainLoop;

/**
 * This is an event for the MainLoop that will pause when called in order to
 * limit the maximum number of frames allowed.
 *
 * @author Joseph Lenton
 */
public class FrameLimitEvent implements MainLoopEvent
{
    // the number of milliseconds each frame should take
    private final long frameTime;

    // the timestamp for the last time the action method was called
    private long lastFrameTime;

    /**
     *
     * It will also limit the frameRate to the given frameRate. This is to
     * ensure that the World runs at a consistent speed. It does not correspond
     * to the vertical sync.
     * If the frameRate is set to 0 then this is disabled.
     * @param frameRate A positive value stating the maximum frameRate, or 0 to run as fast as possible.
     */
    public FrameLimitEvent(final int frameRate)
    {
        if (frameRate < 0) {
            throw new IllegalArgumentException("The frameRate must be a positive number or 0 to disable it, given: " + frameRate);
        }

        if (frameRate == 0) {
            this.frameTime = 0;
        } else {
            this.frameTime = Math.round(1000000000.0 / (double) frameRate);
        }
    }

    /**
     * This should be called once for each frame. When called it will wait the
     * required amount of time to make the average FPS of the main loop
     * approximately equal to the frame rate given in the constructor.
     * @param mainLoop The mainloop calling this FrameLimitEvent.
     */
    @Override
    public <G extends GraphicsGL> void action(MainLoop<G> mainLoop)
    {
        if (frameTime > 0) {
            /* sleep for the rest of the frame */
            long time = frameTime - (System.nanoTime() - lastFrameTime);
            if (time > 0) {
                // sleep properly
                try {
                    long threadWait = time / 1000000l;
                    if (threadWait > 1) {
                        Thread.sleep(threadWait);
                    }
                // or spin for the whole time
                } catch (InterruptedException e) {
                    // the time now minus how long the frame has left to wait for
                    long spinEndTime = System.nanoTime() + (frameTime - (System.nanoTime() - lastFrameTime));
                    while (spinEndTime > System.nanoTime()) {
                        // spin! (do nothing)
                    }
                }
            }

            lastFrameTime = System.nanoTime();
        }
    }
}
